package com.fishbone.sso.config;

import com.fishbone.sso.converter.JwtGrantedAuthoritiesConverter;
import com.fishbone.sso.dynamic.DynamicFilterInvocationSecurityMetadataSource;
import com.fishbone.sso.dynamic.RequestMatcherCreator;
import com.fishbone.sso.entity.WhiteName;
import com.fishbone.sso.handler.FishBoneAccessDeniedHandler;
import com.fishbone.sso.handler.FishBoneAuthenticationEntryPoint;
import com.fishbone.sso.service.MetaResourceService;
import com.fishbone.sso.service.RoleService;
import com.fishbone.sso.service.impl.DefaultMetaResourceService;
import com.fishbone.sso.service.impl.DefaultRoleService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpHeaders;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Configuration
@EnableConfigurationProperties({FishBoneSecureProperties.class})
public class FishBoneSsoDefaultConfiguration {

    @EnableOAuth2Sso
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    static class SsoConfiguration extends WebSecurityConfigurerAdapter {

        private static final String ALLOWED_HEADERS = "*";
        private static final String ALLOWED_METHODS = "*";
        private static final String ALLOWED_ORIGIN = "*";

        @Resource
        private JwtDecoder jwtDecoder;

        @Resource
        private FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource;

        @Resource
        private AccessDecisionManager accessDecisionManager;

        @Resource
        private FishBoneSecureProperties secureProperties;

        private final ApplicationContext applicationContext;

        public SsoConfiguration(ApplicationContext applicationContext) {
            this.applicationContext = applicationContext;
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // http 基础配置
            http.httpBasic().and().csrf().disable();
            // http 跨域配置
            http.cors().configurationSource(corsConfigurationSource());
            http.authorizeRequests(authorizeRequests -> authorizeRequests.antMatchers(passResources()).permitAll()
                    .anyRequest().authenticated()
                    .withObjectPostProcessor(filterSecurityInterceptorObjectPostProcessor()))
                    .oauth2ResourceServer(oauth2ResourceServer ->
                            oauth2ResourceServer
                                    .authenticationEntryPoint(new FishBoneAuthenticationEntryPoint())
                                    .accessDeniedHandler(new FishBoneAccessDeniedHandler())
                                    .jwt(jwtConfigurer -> jwtConfigurer
                                            .jwtAuthenticationConverter(jwtAuthenticationConverter())
                                            .decoder(jwtDecoder)));
            new SsoSecurityConfigurer(this.applicationContext).configure(http);
        }

        /**
         * 手动提取权限
         * @return
         */
        private Converter<Jwt, AbstractAuthenticationToken> jwtAuthenticationConverter() {
            JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
            JwtAuthenticationConverter authenticationConverter = new JwtAuthenticationConverter();
            authenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
            return authenticationConverter;
        }

        /**
         * 自定义 FilterSecurityInterceptor  ObjectPostProcessor 以替换默认配置达到动态权限的目的
         *
         * @return ObjectPostProcessor
         */
        private ObjectPostProcessor<FilterSecurityInterceptor> filterSecurityInterceptorObjectPostProcessor() {
            return new ObjectPostProcessor<FilterSecurityInterceptor>() {
                @Override
                public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                    object.setAccessDecisionManager(accessDecisionManager);
                    object.setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
                    return object;
                }
            };
        }


        /**
         * 跨域配置
         * @return
         */
        private CorsConfigurationSource corsConfigurationSource() {
            CorsConfiguration configuration = new CorsConfiguration();
            configuration.addAllowedHeader(ALLOWED_HEADERS);
            configuration.addAllowedMethod(ALLOWED_METHODS);
            configuration.addAllowedOrigin(ALLOWED_ORIGIN);
            // 暴露哪些头部信息（因为跨域访问默认不能获取全部头部信息）
            configuration.addExposedHeader(HttpHeaders.ACCEPT);
            configuration.setAllowCredentials(true);
            configuration.setMaxAge(18000L);
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            source.registerCorsConfiguration("/**", configuration);
            return source;
        }


        /**
         * 资源白名单
         *
         * @return
         */
        private String[] passResources() {
            List<WhiteName> whiteNames = secureProperties.getWhiteNames();
            if(CollectionUtils.isEmpty(whiteNames)){
                return new String[]{};
            }
            List<String> resources = new ArrayList<>();
            whiteNames.forEach(whiteName -> resources.add(whiteName.getUri()));
            String[] paths = new String[resources.size()];
            return resources.toArray(paths);
        }
    }


    @Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
    String jwkSetUri;

    @Bean
    JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withJwkSetUri(jwkSetUri)
                .build();
    }


    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }


    /**
     * RequestMatcher 生成器
     *
     * @return RequestMatcher
     */
    @Bean
    public RequestMatcherCreator requestMatcherCreator() {
        return metaResources -> metaResources.stream()
                .map(metaResource -> new AntPathRequestMatcher(metaResource.getPattern(), metaResource.getMethod()))
                .collect(Collectors.toSet());
    }


    /**
     * 元数据加载器
     *
     * @return dynamicFilterInvocationSecurityMetadataSource
     */
    @Bean
    public FilterInvocationSecurityMetadataSource dynamicFilterInvocationSecurityMetadataSource() {
        return new DynamicFilterInvocationSecurityMetadataSource();
    }

    /**
     *  角色投票器
     * @return roleVoter
     */
    @Bean
    public RoleVoter roleVoter() {
        return new RoleVoter();
    }

    /**
     *  基于肯定的访问决策器
     *
     * @param decisionVoters  AccessDecisionVoter类型的 Bean 会自动注入到 decisionVoters
     * @return affirmativeBased
     */
    @Bean
    public AccessDecisionManager affirmativeBased(List<AccessDecisionVoter<?>> decisionVoters) {
        return new AffirmativeBased(decisionVoters);
    }

    @Bean
    public MetaResourceService metaResourceService(RestTemplate restTemplate){
        return new DefaultMetaResourceService(restTemplate);
    }


    @Bean
    public RoleService roleService(RestTemplate restTemplate){
        return new DefaultRoleService(restTemplate);
    }
}
